Music-Driven Spiky Chrome Sphere (Unity)
Luna Tian
Overview
This project combines Unity Shader Graph and an audio-reactive control pipeline to build a futuristic
“spiky chrome sphere.” The sphere responds to music in two distinct ways:
High frequencies — control spike amplitude (longer spikes with louder highs).
Low frequencies — drive a heartbeat-like pulse and rotation, adding life-like rhythm.
The result is a reflective sculpture that feels alive, pulsing and twisting to the soundtrack.
Your browser does not support the video tag.
Demo Video 1 — Music: Scorpion - City pop Type Beat
Your browser does not support the video tag.
Demo Video 2 — Music: Trap Synthwave Beat
Details
Shader Graph — Triplanar noise displacement along normals creates procedural spikes, modulated by _SpikeAmp
and _Pulse
.
Working Space
Final Parameter Settings
Parameter Value Description
SpikeAmp Dynamic Base spike amplitude
NoiseScale 5.58 Controls density of spikes
FlowSpeed 0.105 Speed of procedural noise flow
Sharpness 1.9 Controls spike sharpness
Threshold 0.156 Activation threshold for spike mask
Pulse Dynamic Heartbeat intensity driven by low-frequency band
Shader Math (Vertex Displacement)
Idea: approximate 3D noise via triplanar projections, then displace vertices along their normals so spikes grow outward from the sphere.
When _SpikeAmp = 0
, the surface is perfectly smooth.
Inputs
Object-space position: \( \mathbf{p} = (x,y,z) \)
Object-space normal: \( \mathbf{n} \) (normalized)
Time-driven flow: \( t = \text{Time} \cdot \_FlowSpeed \)
Triplanar UVs & Noises
\[
\mathbf{uv}_{xy} = (x, y)\cdot \_NoiseScale + (t, t), \quad
\mathbf{uv}_{yz} = (y, z)\cdot \_NoiseScale + (1.37t, 1.37t), \quad
\mathbf{uv}_{zx} = (z, x)\cdot \_NoiseScale + (2.11t, 2.11t)
\]
Let \( n_{xy}=\text{Noise}(\mathbf{uv}_{xy}),\; n_{yz}=\text{Noise}(\mathbf{uv}_{yz}),\; n_{zx}=\text{Noise}(\mathbf{uv}_{zx}) \in [0,1]\).
Triplanar Weights
\[
\mathbf{a} = |\mathbf{n}| = (|n_x|, |n_y|, |n_z|),\quad
s = a_x + a_y + a_z + \varepsilon,\;\; \varepsilon=10^{-5}
\]
\[
\mathbf{w} = \frac{\mathbf{a}}{s} = (w_x, w_y, w_z),\;\; w_x+w_y+w_z \approx 1
\]
Approximate 3D Noise
\[
\text{noise}_{01} = w_x \cdot n_{xy} + w_y \cdot n_{yz} + w_z \cdot n_{zx} \in [0,1]
\]
Spike Mask & Amplitude
\[
\text{noise}_{\text{sharp}} = \left(\text{clamp}(\text{noise}_{01},\,0,1)\right)^{\_Sharpness}
\]
\[
\text{mask} = \text{smoothstep}(\_Threshold,\, 1,\, \text{noise}_{\text{sharp}})
\]
\[
\text{amp} = \text{mask} \cdot \_SpikeAmp \cdot \left(1 + 0.2 \cdot \_Pulse\right)
\]
Normal Displacement
\[
\mathbf{p}_{\text{out}} = \mathbf{p} + \mathbf{n} \cdot \text{amp}
\]
Feed \( \mathbf{p}_{\text{out}} \) into the Shader Graph’s Vertex Position .
Music Scripting Methodology
Signal flow for controlling _SpikeAmp
(high/overall energy) and _Pulse
(low-frequency heartbeat):
1. Spectrum Acquisition
Compute FFT spectrum each frame: \( \text{spec}[i] = \text{FFT}(\text{audio}) \)
Use a high-quality window (e.g., Blackman-Harris) to reduce spectral leakage.
2. Spike Path — High/Overall Energy → _SpikeAmp
RMS-like volume:
\[
v = \sqrt{\frac{1}{N}\sum_i \text{spec}[i]^2}
\]
Apply gain & compression, clamp to \([0,1]\).
Envelope smoothing (attack/decay):
\[
v_{\text{env}} \leftarrow \text{lerp}(v_{\text{env}}, v,\; k_{\text{rise/fall}}\Delta t)
\]
Map to spike:
\[
\_SpikeAmp = \text{clamp01}(\text{baseSpikeAmp} + v_{\text{env}} \cdot \text{spikeAmpMax})
\]
3. Pulse Path — Low-Frequency Focus → _Pulse
Choose a kick-focused band around \(f_c \approx 70\text{–}100\text{ Hz}\) with bandwidth \(B\) (e.g., 80 Hz).
Band energy:
\[
E_{\mathrm{low}} =
\sum_{f\in\,[f_c-\frac{B}{2},\,f_c+\frac{B}{2}]}
\mathrm{spec}[f]^2
\]
Onset (spectral flux, positive changes only):
\[
\text{flux} = \sum_{f}\max(\text{spec}[f]-\text{spec}_{\text{prev}}[f],\;0)
\]
Envelope both signals with fast attack / slower release:
\[
E_{\text{env}},\; \text{flux}_{\text{env}}
\]
Energy shaping (gamma, tail/decay for heartbeat feel):
\[
\tilde{E}=\text{clamp01}\big((E_{\text{low}}\cdot s)^{\gamma}\big),\quad
\text{beatEnv}=\max(e^{-\lambda \Delta t}\cdot \text{beatEnv},\; \tilde{E})
\]
Combine energy + onset:
\[
c=\text{clamp01}(w_E\cdot \text{beatEnv}+w_O\cdot \text{flux}_{\text{env}})
\]
Bias & output gain:
\[
\_Pulse=\text{clamp01}\big(b+(1-b)\cdot c\big)\cdot g_{\text{out}}
\]
4. Rotation Coupled to Pulse
Angular speed:
\[
\omega = \text{lerp}(\omega_{\min}, \omega_{\max}, \_Pulse)\cdot s_{\text{spin}}
\]
Rotate around a fixed world-space axis per frame:
\(\Delta \theta = \omega \Delta t\).
Takeaways
Shader Graph is essentially math expressed visually ; understanding noise → mask → displacement is the key.
Separating high vs. low frequency bands made the visual response much more expressive.
Different tracks require different parameter tuning — experimentation is part of the process.
Adding rotation linked to pulse gave the sculpture a stronger sense of presence, beyond just surface changes.